package com.freedom.supercoin.network;

import android.os.Bundle;
import android.support.annotation.Nullable;


import com.freedom.supercoin.base_library.utils.LogUtils;

import java.util.concurrent.TimeUnit;

import rx.Notification;
import rx.Observable;
import rx.Observable.Transformer;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.exceptions.Exceptions;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.schedulers.Schedulers;

//RxUtil
public class RxUtils {
    public static <T> Transformer<T, T> withEvent(@Nullable final Bundle args) {
        return new Transformer<T, T>() {
            @Override
            public Observable<T> call(Observable<T> tObservable) {
                return tObservable.doOnEach(new Action1<Notification<? super T>>() {
                    public void call(Notification<? super T> notification) {
                        // EventBus.getDefault().post(new RequestEvent(notification, args));
                    }
                });
            }
        };
    }


    //切换线程
    public static <T> Transformer<T, T> applyIOSchedulers() {
        return new Transformer<T, T>() {
            public Observable<T> call(Observable<T> observable) {
                return observable.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }

    public static <T> Func1<Result<T>, Observable<Result<T>>> flatCheckResult() {
        return new Func1<Result<T>, Observable<Result<T>>>() {
            public Observable<Result<T>> call(Result<T> result) {
                ClientException error = result.checkResult();
                if (error != null) {
                    return Observable.error(error);
                }
                return Observable.just(result);
            }
        };
    }


    public static <T> Func1<Result<T>, Result<T>> checkResult() {
        return new Func1<Result<T>, Result<T>>() {
            public Result<T> call(Result<T> result) {
                ClientException error = result.checkResult();
                if (error == null) {
                    return result;
                }
                throw Exceptions.propagate(error);
            }
        };
    }


    public static <T> Func1<Result<T>, Observable<T>> flatCheckResultToData() {
        return new Func1<Result<T>, Observable<T>>() {
            public Observable<T> call(Result<T> result) {
                ClientException error = result.checkResult();
                if (error != null) {
                    return Observable.error(error);
                }
                return Observable.just(result.getBody());
            }
        };
    }


    //检查Result
    public static <T> Func1<Result<T>, T> checkResultToData() {
        return result -> {
            ClientException error = result.checkResult();
            if (error == null) {
                if (result.getBody() != null) {
                    return result.getBody();
                } else if (result.getData() != null) {
                    return result.getData();
                } else {
                    return null;
                }
            }
            throw Exceptions.propagate(error);
        };
    }


    public static <T extends Result> Action1<T> action1CheckResult() {
        return result -> {
            ClientException error = result.checkResult();
            if (error != null) {
                throw Exceptions.propagate(error);
            }
        };
    }


    public static void unsubscribe(Subscription subscription) {
        if (subscription != null && !subscription.isUnsubscribed()) {
            subscription.unsubscribe();
        }
    }


    public static <T> Transformer<Result<T>, Result<T>> apiCommon() {
        return new Transformer<Result<T>, Result<T>>() {
            public Observable<Result<T>> call(Observable<Result<T>> observable) {
                return observable.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .flatMap(RxUtils.<T>flatCheckResult());
            }
        };
    }


    public static <T> Transformer<Result<T>, T> apiCommonToData() {
        return new Transformer<Result<T>, T>() {
            public Observable<T> call(Observable<Result<T>> observable) {
                return observable.subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .flatMap(RxUtils.<T>flatCheckResultToData());
            }
        };
    }


    public static <T> Transformer<T, T> retryWhenEmitNull() {
        return retryWhenEmitNull(3);
    }


    private static <T> Transformer<T, T> retryWhenEmitNull(final int count) {
        return new Transformer<T, T>() {
            public Observable<T> call(Observable<T> observable) {
                return observable.doOnNext(new Action1<T>() {
                    public void call(T t) {
                        if (t == null) {
                            throw new RuntimeException("");
                        }
                    }
                }).retryWhen(new Func1<Observable<? extends Throwable>, Observable<?>>() {
                    public Observable<?> call(Observable<? extends Throwable> errors) {
                        return errors.zipWith(
                                Observable.range(1, count), new Func2<Throwable, Integer, Integer>() {
                                    public Integer call(Throwable throwable, Integer i) {
                                        if (i.intValue() >= count ||
                                                !(throwable instanceof RuntimeException)) {
                                            throw Exceptions.propagate(throwable);
                                        }
                                        LogUtils.ShowE("emit a null value from remote service",
                                                new Object[0].toString());
                                        LogUtils.ShowD("[API] We will retry: %d", +i+"");
                                        return i;
                                    }
                                }).flatMap(new Func1<Integer, Observable<Long>>() {
                            public Observable<Long> call(Integer retryCount) {
                                LogUtils.ShowE("[API] Retry count: %d, delay: %ds", retryCount+ (long) retryCount+"");
                                return Observable.timer((long) retryCount, TimeUnit.SECONDS);
                            }
                        });
                    }
                });
            }
        };
    }
}
